#pragma once
#include "Util/Log.hpp"
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <unordered_map>
#include <mutex>

using namespace LogModel;

using WsServer_t = websocketpp::server<websocketpp::config::asio>;

enum User_Status
{
    LOGIN,
    UNLOGIN
};

class Session
{
public:
    Session(int ssid) : _ssid(ssid), _status(LOGIN), _timer(WsServer_t::timer_ptr())
    {
        LOG(LogLevel::INFO) << "session " << this << " is create" << " ssid=" << ssid;
    }
    ~Session()
    {
        LOG(LogLevel::INFO) << "session " << this << " is destroy" << " ssid=" << _ssid;
    }

public:
    void set_uid(int uid)
    {
        _uid = uid;
    }
    int get_uid()
    {
        return _uid;
    }
    int get_ssid()
    {
        return _ssid;
    }
    bool is_login()
    {
        return _status == LOGIN;
    }
    void set_status(User_Status &status)
    {
        _status = status;
    }
    void set_session_timer(WsServer_t::timer_ptr timer)
    {
        _timer = timer;
    }
    WsServer_t::timer_ptr &get_timer()
    {
        return _timer;
    }

private:
    int _ssid;                    // 标识唯一的会话
    int _uid;                     // 用户的用户id
    User_Status _status;          // 用户状态
    WsServer_t::timer_ptr _timer; // 当前session的定时器
};

#define SESSION_PERMANENT -1      // 标志会话属于永久会话
#define SESSION_TIMEOUT 30 * 1000 // 会话的过期时间: 30秒
using session_ptr = std::shared_ptr<Session>;
class Session_manager
{
public:
    // 将服务器对象传进来
    Session_manager(WsServer_t *server) : _server(server), _next_ssid(1)
    {
        LOG(LogLevel::INFO) << "session管理器初始化完毕";
    }
    ~Session_manager()
    {
    }

public:
    // 创建session
    session_ptr create_session(int uid, User_Status status)
    {
        {
            std::lock_guard<std::mutex> lock(_mutex);
            // 新建会话
            session_ptr session(new Session(_next_ssid));
            // 设置会话的基本信息
            session->set_status(status);
            session->set_uid(uid);
            // 保存和管理会话
            _sessions[_next_ssid] = session;
            // 计数器++
            _next_ssid++;
            return session;
        }
    }
    // 获取session
    session_ptr get_session(int ssid)
    {
        {
            std::lock_guard<std::mutex> lock(_mutex);
            auto it = _sessions.find(ssid);
            if (it == _sessions.end())
            {
                return NULL;
            }
            return it->second;
        }
    }
    // 为某个session设置定时任务和过期时间
    void set_session_timer(int ssid, int ms) // ms : 单位毫秒
    {
        // 依赖于websocketpp的定时器来实现session定时器生命周期的管理

        // session的生命周期:
        // (1). 在客户端与服务器以http协议通信的时候(登录、注册),session应当具备生命周期,即在指定时间无通信后,
        // 删除session
        // (2). 但是当客户端与服务端建立websocket长连接后(此时玩家处于游戏大厅或游戏房间),那么session就该是永久存在的
        // 否则玩家可能只是许久未下棋或者聊天而已,如果此时删除了session,玩家就处于未登录状态.
        // 注意: session在创建之后,本身是永久存在的(不销毁不就是永久存在的咯？)

        // session声明周期的变换:
        // (3). 登录之后,创建session之后,设置session在指定时间无通信后删除.
        // (4). 进入游戏大厅或者游戏房间后,session就需要被设置为永久存在
        // (5). 退出游戏大厅后,session又要被设置为临时的,在指定时间无通信后删除.

        // 1.获取会话
        session_ptr sp = get_session(ssid);
        if (sp.get() == nullptr) // session不存在
        {
            return;
        }
        // 2.获取该会话的定时器:
        WsServer_t::timer_ptr tp = sp->get_timer();

        // 3.在session永久存在的情况下,设置为永久存在
        if (tp.get() == nullptr && ms == SESSION_PERMANENT)
        {
            return; // 因为本身是永久的,所以不需要做额外的事情
        }
        // 4.在session永久存在的情况下,设置为在指定时间无通信后删除
        else if (tp.get() == nullptr && ms != SESSION_PERMANENT) // tp底层也是智能指针
        {
            // 注意: 要给destroy_session绑定this和ssid,因为定时器调用该函数时,不会帮我们传任何参数,需要我们自己绑定
            // 如果不绑定this,定时器就无法知道该函数是哪个Session_manager对象的。
            // 并且要: &Session_manager:: 指定和取地址
            WsServer_t::timer_ptr tmp_tp =
                _server->set_timer(ms, std::bind(&Session_manager::destroy_session, this, ssid));
            // 为当前会话重新添加定时器
            sp->set_session_timer(tmp_tp);
        }
        // 5.在session是处于定时删除的情况下,将其设置为永久存在,即解除掉定时任务
        else if (tp.get() != nullptr && ms == SESSION_PERMANENT)
        {
            // 要注意: 解除定时任务时，定时任务被解除时，定时任务会被立即执行！
            // 而因为定时任务是删除对应session在_sessions的管理,然后销毁该session对象
            // 所以只需要将一个一模一样的session创建然后保存到_sessions即可

            // 5.1获取当前session内部的uid:
            int uid = sp->get_uid();
            // 5.2解除指定session的定时任务:
            tp->cancel();
            // 5.3构造出和原来一模一样的session并管理起来:

            // 注意: 构造出新的session后不能立即添加到_sessions里,因为上面的解除定时任务,不是
            //      立即被执行的,即可能下面这里插入了the_session之后,定时任务才被执行,于是就导致
            //      定时任务里面erase了the_session.
            // 有bug的做法:
            // session_ptr the_session(new Session(ssid));
            // the_session->set_uid(uid);
            // _sessions[ssid] = the_session;

            // 正确做法: 使用定时器
            session_ptr the_session(new Session(ssid));
            the_session->set_uid(uid);
            _server->set_timer(0, std::bind(&Session_manager::append_session, this, the_session));
            // 注意：虽然这里时间设置为0ms，但该定时任务并不会立即执行
            // 5.4 还要将该会话的定时器设置为空:
            the_session->set_session_timer(WsServer_t::timer_ptr());
        }
        // 6. 在session是处于定时删除的情况下,重置其定时删除的时间
        else if (tp.get() != nullptr && ms != SESSION_PERMANENT)
        {
            // 做法: 取消原本的定时任务,重新构造并添加新的定时任务
            // 6.1获取该ssid对应的会话的用户id
            int uid = sp->get_uid();
            // 6.2 取消原本的定时任务(任务是销毁当前会话)
            tp->cancel();
            // 6.3由于ssid对应的会话已经被销毁,所以需要重新创建一个一样的会话,然后通过定时器来将该会话添加到
            //_sessions中,原因和5.一样
            session_ptr new_session(new Session(ssid));
            new_session->set_uid(uid);
            _server->set_timer(0, std::bind(&Session_manager::append_session, this, new_session));

            // 为该会话创建一个新的定时器: 任务就是指定时间ms后删除当前会话
            WsServer_t::timer_ptr new_timer =
                _server->set_timer(ms, std::bind(&Session_manager::destroy_session, this, ssid));

            // 还要将该会话的定时器设置为新的定时器:
            new_session->set_session_timer(new_timer);
        }
    }

private:
    void append_session(session_ptr &sp)
    {
        std::lock_guard<std::mutex> lock(_mutex);
        _sessions[sp->get_ssid()] = sp;
    }
    // 销毁某个session
    void destroy_session(int ssid)
    {
        {
            std::lock_guard<std::mutex> lock(_mutex);
            _sessions.erase(ssid);
            // 如果其他地方都不再使用该ssid对应的session_ptr，那么只剩_sessions引用着该session_ptr,
            // 而这里erase后,该ssid对应的session_ptr的引用计数为0,session对象就被销毁掉
        }
    }

private:
    int _next_ssid;                                 // ssid计数器
    WsServer_t *_server;                            // 指向服务器对象,用来添加定时器
    std::unordered_map<int, session_ptr> _sessions; // 管理所有Session
    std::mutex _mutex;                              // 多线程下,保护计数器和哈希桶
};